package com.tianxiao.faas.application.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tianxiao.faas.biz.corner.service.user.UserLoginService;
import com.tianxiao.faas.biz.corner.service.user.UserService;
import com.tianxiao.faas.biz.domain.entity.UserExt;
import com.tianxiao.faas.biz.service.corner.UserLogService;
import com.tianxiao.faas.common.access.Access;
import com.tianxiao.faas.common.exception.biz.AuthenticationException;
import com.tianxiao.faas.common.exception.biz.PermissionException;
import com.tianxiao.faas.mapper.model.UsersModel;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Aspect
public class AuthenticationAopConfig {
    static Logger logger = LoggerFactory.getLogger("AuthenticationAopConfig");
    @Resource
    private UserLoginService userLoginService;
    @Resource
    private UserService userService;
    @Resource
    private UserLogService userLogService;
    @Value("${spring.server.domain}")
    private String domain;

    @Pointcut("@annotation(com.tianxiao.faas.common.access.Access)")
    public void pointCut() {

    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = getToken(request);
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Access annotation = methodSignature.getMethod().getAnnotation(Access.class);
        if (annotation.login() && StringUtils.isBlank(token)) {
            throw new AuthenticationException("用户未登录");
        }
        Long userId = userLoginService.getLoginUserId(token);
        if (annotation.login() && userId == null) {
            throw new AuthenticationException("用户未登陆，请重新登陆");
        }
        AuthenticationContext context = new AuthenticationContext(userId);
        AuthenticationContextHolder.setContext(context);

        Object[] args = proceedingJoinPoint.getArgs();
        String methodName = signature.getName();
        checkPermission(annotation, userId, args);
        String[] parameterNames = methodSignature.getParameterNames();
        Integer userParamIndex = findIndex(parameterNames, "userId");
        long l = System.currentTimeMillis();
        Object proceed = null;
        boolean error = false;
        try {
            if (userParamIndex != null) {
                args[userParamIndex] = userId;
                proceed = proceedingJoinPoint.proceed(args);
            } else {
                proceed = proceedingJoinPoint.proceed();
            }
        }catch (Throwable e) {
            error = true;
            throw e;
        } finally {
            AuthenticationContextHolder.remove();
            if (logger.isInfoEnabled()) {
                List<Object> args2 = new ArrayList<>();
                for (Object arg : args) {
                    if (arg instanceof InputStreamSource || arg instanceof InputStream || arg instanceof OutputStream) {
                        continue;
                    }
                    if (arg instanceof Serializable) {
                        args2.add(arg);
                    }
                }
                logger.info("AuthenticationWebListener-setContext:{}, method:{} param:{} error:{} cost:{} ms", context, methodName, JSON.toJSONString(args2)
                        , error
                        , System.currentTimeMillis() - l);
            }
        }
        return proceed;
    }

    private void checkPermission(Access annotation, Long userId, Object[] args) {
        if (annotation != null && annotation.access().length > 0) {
            UsersModel byId = userService.getById(userId);
            if (byId == null) {
                throw new AuthenticationException("用户不存在" + userId);
            }
            String valueBox = byId.getValueBox();
            if (valueBox == null) {
                throw new PermissionException("该用户权限不足，请联系关系员");
            }
            if (valueBox != null) {
                UserExt userExt = JSONObject.parseObject(valueBox, UserExt.class);
                List<String> permissions = userExt.getAccess();
                if (permissions == null) {
                    throw new PermissionException("该用户权限不足，请联系关系员");
                }
                for (String ac : annotation.access()) {
                    if (!permissions.contains(ac)) {
                        throw new PermissionException("该用户权限不足，请联系关系员");
                    }
                }
                String action = annotation.action();
                if (StringUtils.isNotBlank(action)) {
                    userLogService.log(userId, action, args);
                }
            }
        } else if(annotation != null && StringUtils.isNotBlank(annotation.action())) {
            String action = annotation.action();
            if (StringUtils.isNotBlank(action)) {
                userLogService.log(userId, action, args);
            }
        }
    }

    public static String getToken(HttpServletRequest request) {
        String token = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie co : cookies) {
                String name = co.getName();
                if (Access.TOKEN.equalsIgnoreCase(name)) {
                    token = co.getValue();
                    break;
                }

            }
        }
        if (token == null) {
            String header = request.getHeader(Access.TOKEN);
            token = header;
        }
        return token;
    }

    private Integer findIndex(String[] parameterNames, String paramName) {
        Integer userParamIndex = null;
        for (int i = 0; i < parameterNames.length; i++) {
            if (parameterNames[i].equalsIgnoreCase(paramName)) {
                userParamIndex = i;
                break;
            }
        }
        return userParamIndex;
    }

}
